/**
 * Generate code from /{{projectName}}-core/src/main/java/{{packageName}}/util/security/AuthUserServiceImpl.java.hbs
 */
// @SkipOverwrite
package cn.ibizlab.util.security;

import cn.ibizlab.core.authentication.domain.AuthLogin;
import cn.ibizlab.core.authentication.domain.AuthUser;
import cn.ibizlab.core.authentication.domain.AuthUserImpl;
import cn.ibizlab.core.authentication.mapping.AuthInfoMapping;
import cn.ibizlab.core.authentication.service.AuthLoginService;
import cn.ibizlab.core.authentication.service.AuthUserService;
import cn.ibizlab.util.common.CacheStore;
import cn.ibizlab.util.enums.Entities;
import cn.ibizlab.util.errors.BadRequestAlertException;
import cn.ibizlab.util.service.AuthenticationUserService;
import cn.ibizlab.util.service.UserServiceAdapter;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.security.PrivateKey;
import java.text.SimpleDateFormat;
import java.util.*;

@Service
@Slf4j
public class AuthUserServiceImpl implements AuthenticationUserService {

    protected static final String SIGNATURE_RSA = "RSA";
    protected static final String SIGNATURE_MAC = "MAC";

    protected SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Autowired
    @Lazy
    private AuthUserService authUserService;

    @Autowired
    @Lazy
    private AuthLoginService loginService;

    @Autowired
    @Lazy
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthInfoMapping authInfoMapping;

    @Autowired
    @Lazy
    private JwkStore jwkStore;

    @Override
    @Cacheable( value="ibzuaa_users", key = "#root.target.systemId+':getByUsername:'+#p0")
    public AuthenticationUser loadUserByUsername(String username) {
        AuthUser authUser = authUserService.loadUserByUsername(username);
        return createAuthenticationUser(authInfoMapping.toAuthUser20Impl((AuthUserImpl)authUser));
    }

    protected AuthenticationUser createAuthenticationUser(AuthenticationUser adapterUser) {
        AuthenticationUser user = adapterUser;
        return user;
    }

    @Override
    public AuthenticationInfo loadUserByLogin(String username, String password) {
        AuthLogin login = loginService.login(new AuthLogin().setLoginName(username).setPassword(password));
        return new AuthenticationInfo(login.getToken(),authInfoMapping.toAuthenticationInfo(login.getUser()));
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value = {"ibzuaa_users"}, key = "#root.target.systemId+':getByUsername:'+#p0"),
            @CacheEvict(value = {"ibzuaa_users"}, key = "'glob:*getByUsername:'+#p0")
    })
    public void resetByUsername(String username) {
        log.info("重置用户{}缓存",username);
    }

    @Caching(evict = {
            @CacheEvict(value = {"ibzuaa_users"}, key = "#root.target.systemId+':getByUsername:'+#p0"),
            @CacheEvict(value = {"ibzuaa_users"}, key = "'glob:*getByUsername:'+#p0")
    })
    public void logout(String username) {
        CacheStore.getInstance().clearL2ByPattern("ibiz-cloud-uaa-cat-*--*");
        CacheStore.getInstance().clearL2ByPattern("ibiz-cloud-uaa-user-*");
        log.info("注销用户{}",username);
    }

    @Caching(evict = {
            @CacheEvict(value = {"ibzuaa_users"}, key = "#root.target.systemId+':getByUsername:'+#p0"),
            @CacheEvict(value = {"ibzuaa_users"}, key = "'glob:*getByUsername:'+#p0")
    })
    public void revokeToken(String username, int minExpiresIn) {

    }

    @Value("${ibiz.enablePermissionValid:false}")
    boolean enablePermissionValid;  //是否开启权限校验

    @Value("${ibiz.systemid}")
    private String systemId;

    public String getSystemId() {
        return this.systemId;
    }

    @Override
    public Map getAppData() {
        Map<String,Object> appData = new HashMap<>() ;
        Set<String> appMenu = new HashSet<>();
        Set<String> uniRes = new HashSet<>();
        AuthenticationUser user = AuthenticationUser.getAuthenticationUser();
        Collection<GrantedAuthority> authorities = user.getAuthorities();
        if(enablePermissionValid && !ObjectUtils.isEmpty(systemId) && !ObjectUtils.isEmpty(authorities)){
            authorities.forEach(item->{
                String authority=item.getAuthority();
                if(authority.startsWith(systemId))
                    uniRes.add(authority);
                if (authority.startsWith(String.format("%1$s-APPMENU",systemId)))
                    appMenu.add(authority.substring(systemId.length()+9));
            });
        }
        if (!ObjectUtils.isEmpty(user.getExpiration())) {
            appData.put("expireddate", UserServiceAdapter.dtFormat.format(user.getExpiration()));
        }
        Map<String,Object> context = new HashMap<>();
        context.putAll(user.getSessionParams());
        context.put("srfusername",user.getPersonName());
        appData.put("context",context);
        appData.put("unires",uniRes);
        appData.put("appmenu",appMenu);
        appData.put("enablepermissionvalid", !user.isSuperUser() && enablePermissionValid);
        return appData;
    }

    @Value("${ibiz.jwt.secret:ibzsecret}")
    private String secret;

    @Value("${ibiz.jwt.expiration:7200000}")
    private Long expiration;

    @Value("${ibiz.jwt.signature:MAC}")
    private String signature;

    /**
     * 生成token
     * @param userDetails
     * @return
     */
    public String generateToken(UserDetails userDetails) {
        return (String)generateAccessToken(userDetails);
    }

    public Object generateAccessToken(UserDetails userDetails) {
        if(ObjectUtils.isEmpty(signature)|| SIGNATURE_MAC.equalsIgnoreCase(signature))
            return generateTokenByMAC(userDetails);
        else if(SIGNATURE_RSA.equalsIgnoreCase(signature))
            return generateTokenByRSA(userDetails);
        else
            throw new BadRequestAlertException(String.format("生成访问令牌出错，签名暂未支持[%1$s]加密算法",signature), Entities.AUTH_USER.toString(),userDetails.getUsername());
    }
    public Object refreshAccessToken(String refreshTokenValue) {
        log.info("刷新token：{}",refreshTokenValue);
        return null;
    }

    /**
     * 通过MAC方式生成token（对称加密）
     * @param userDetails
     * @return
     */
    @SneakyThrows
    public String generateTokenByMAC(UserDetails userDetails){
        Date now = new Date();
        JWSSigner signer = new MACSigner(secret);
        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS512)
                .contentType("JWS")
                .build();
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .subject(userDetails.getUsername())
                .issueTime(now)
                .expirationTime(new Date(now.getTime()+expiration))
                .build();
        SignedJWT signedJWT = new SignedJWT(header,claimsSet);
        signedJWT.sign(signer);
        return signedJWT.serialize();
    }

    /**
     * 通过RSA方式生成token（非对称加密）
     * @param userDetails
     * @return
     */
    @SneakyThrows
    public String generateTokenByRSA(UserDetails userDetails) {
        PrivateKey privateJWK = jwkStore.getPrivateKey();
        Date now = new Date();
        JWSSigner signer = new RSASSASigner(privateJWK);
        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
                .contentType("JWS")
                .build();
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .subject(userDetails.getUsername())
                .issueTime(now)
                .expirationTime(new Date(now.getTime()+expiration))
                .build();
        SignedJWT signedJWT = new SignedJWT(header,claimsSet);
        signedJWT.sign(signer);
        return signedJWT.serialize();
    }

    /**
     * token校验
     * @param token
     * @param userDetails
     * @return
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        if(ObjectUtils.isEmpty(signature)|| SIGNATURE_MAC.equalsIgnoreCase(signature))
            return validateTokenByMAC(token,userDetails);
        else if(signature.equalsIgnoreCase((signature)))
            return validateTokenByRSA(token,userDetails);
        else
            throw new BadRequestAlertException(String.format("验证访问令牌出错，签名暂未支持[%1$s]加密算法",signature),Entities.AUTH_USER.toString(),userDetails.getUsername());
    }

    /**
     * 通过MAC方式验证token（对称加密）
     * @param token
     * @param userDetails
     * @return
     */
    @SneakyThrows
    public Boolean validateTokenByMAC(String token, UserDetails userDetails) {
        Date now = new Date();
        JWSVerifier verifier = new MACVerifier(secret);
        SignedJWT signedJWT = SignedJWT.parse(token);
        JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
        if(signedJWT.verify(verifier) && now.before(claimsSet.getExpirationTime())){
            AuthenticationUser user = (AuthenticationUser) userDetails;
            user.set("token", token);
            user.set("expiration",claimsSet.getExpirationTime());
            return true;
        }
        return false;
    }

    /**
     * 通过RSA方式验证token（非对称加密）
     * @param token
     * @param userDetails
     * @return
     */
    @SneakyThrows
    public Boolean validateTokenByRSA(String token, UserDetails userDetails) {
        Date now = new Date();
        JWSVerifier verifier = jwkStore.getJWSVerifier();
        SignedJWT signedJWT = SignedJWT.parse(token);
        JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();

        if(!signedJWT.verify(verifier))
            return false;

        if(now.after(claimsSet.getExpirationTime()))
            throw new BadRequestAlertException(String.format("访问令牌已过期，令牌有效期为[%1$s]，当前时间为[%2$s]",sdf.format(claimsSet.getExpirationTime()),sdf.format(now)),Entities.AUTH_USER.toString(),userDetails.getUsername());

        AuthenticationUser user = (AuthenticationUser) userDetails;
        user.set("token", token);
        user.set("expiration",claimsSet.getExpirationTime());
        return true;
    }

    @SneakyThrows
    public String getUsernameFromToken(String token) {
        SignedJWT signedJWT = SignedJWT.parse(token);
        JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
        return claimsSet.getSubject();
    }

    @SneakyThrows
    public Date getExpirationDateFromToken(String token) {
        SignedJWT signedJWT = SignedJWT.parse(token);
        JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
        return claimsSet.getExpirationTime();
    }



    /**
     * 获取公钥
     * @return
     */
    @Override
    public String getSignatureKey() {
        return jwkStore.getPublicJWK().toJSONString();
    }


}